package com.example.lottery_system.service.mq;

import cn.hutool.core.date.DateUtil;
import com.example.lottery_system.common.exception.ServiceException;
import com.example.lottery_system.common.utils.JacksonUtil;
import com.example.lottery_system.common.utils.MailUtil;
import com.example.lottery_system.common.utils.SMSUtil;
import com.example.lottery_system.contoller.param.DrawPrizeParam;
import com.example.lottery_system.dao.dataobject.ActivityPrizeDO;
import com.example.lottery_system.dao.dataobject.WinningRecordDO;
import com.example.lottery_system.dao.mapper.ActivityPrizeMapper;
import com.example.lottery_system.dao.mapper.WinningRecordMapper;
import com.example.lottery_system.service.DrawPrizeService;
import com.example.lottery_system.service.activitystatus.ActivityStatusManager;
import com.example.lottery_system.service.dto.ConvertActivityStatusDTO;
import com.example.lottery_system.service.enums.ActivityPrizeStatus;
import com.example.lottery_system.service.enums.ActivityPrizeTiers;
import com.example.lottery_system.service.enums.ActivityStatus;
import com.example.lottery_system.service.enums.ActivityUserStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.example.lottery_system.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    private final static Logger logger = LoggerFactory.getLogger(MqReceiver.class);

    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;


    @RabbitHandler
    public void process(Map<String, String> message) {
        // 成功接收消息
        logger.info("MQ成功接收到消息，message:{}", JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);

        // 处理抽奖流程
        try {
            // 校验抽奖请求是否有效
            if(!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }

            // 状态扭转处理(!!) // 设计模式
            statusConvert(param);

            // 保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecords(param);

            // 通知中奖者（邮箱、短信）
            // 抽奖之后的后续流程，异步（并发）处理
            syncExecute(winningRecordDOList);

        } catch (ServiceException e) {
            logger.error("处理MQ消息异常! {}:{}", e.getCode(), e.getMessage(), e);
            // 需要保证事务一致性（回滚）
            rollback(param);


            // 抛出异常：消息重试（解决异常：代码bug、网络问题、服务问题）
            throw e;

        } catch (Exception e) {
            logger.error("处理MQ消息异常!", e);
            // 如果异常，需要保证事务的一致性（回滚）


            // 抛出异常
            throw e;
        }
    }

    /**
     * 处理抽奖异常的回滚行为
     * 回滚：恢复处理请求之前的库表状态
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        // 1、回滚状态：活动、奖品、人员
        // 状态是否需要回滚
        if(!statusNeedRollback(param)) {
            return;
        }
        rollbackStatus(param);
        // 2、回滚中奖者名单
        // 是否需要回滚
        if(!winnerNeedRollback(param)) {
            return;
        }
        RollbackWinner(param);
    }

    /**
     * 回滚：中奖记录，删除奖品下的中奖者
     * @param param
     */
    private void RollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        // 判断活动中的奖品
        int count = winningRecordMapper.countByActivityIdAndPrizeId(param.getActivityId(), param.getPrizeId());
        return count > 0;
    }

    /**
     * 回滚：恢复相关状态
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        //状态恢复，使用ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatus.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetActivityPrizeStatus(ActivityPrizeStatus.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetActivityUserStatus(ActivityUserStatus.INIT);

        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        // 判断活动、奖品、人员状态是否已经扭转
        // 扭转状态时，保证了事务的一致性。（不包含活动）
        // 只用判断人员/奖品是否扭转，就能判断状态是否全部扭转。不能判断活动是否扭转。

        // prize
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByActivityPrizeId(param.getActivityId(), param.getPrizeId());
        if(activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatus.COMPLETED.name())) {

            // 已扭转，要回滚
            return true;
        }
        return false;
    }

    /**
     * 并发处理抽奖后续流程
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        // 通过线程池 threadPoolTaskExecutor

        // 短信通知
        threadPoolTaskExecutor.execute(() -> sendMessage(winningRecordDOList));

        // 邮件通知
        threadPoolTaskExecutor.execute(() -> sendMail(winningRecordDOList));

    }

    /**
     * 发邮件
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空，不用发邮件！");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList) {
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知",
                    "xxxxx要拼接 -_- xxxx");
        }
    }

    /**
     * 发短信
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空，不用发短信！");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList) {
            Map<String, String> map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName() );
            map.put("activityTiers", ActivityPrizeTiers.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));
            smsUtil.sendMessage("SMS_475770118",
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(map));
        }
    }

    /**
     * 状态扭转
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatus.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetActivityPrizeStatus(ActivityPrizeStatus.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetActivityUserStatus(ActivityUserStatus.COMPLETED);

        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }



    /**
     * 状态扭转
     * @param param
     */
    private void statusConvertTest(DrawPrizeParam param) {
        // 问题
        // 1、活动状态有依赖性，导致代码维护性差
        // 2、状态扭转条件可能会扩展，当前写法，扩展性差，维护性差
        // 3、代码的灵活性、扩展性、维护性差
        // 解决方案：设计模式（责任链设计模式、策略设计模式）


        // 活动：RUNNING -> COMPLETED 全部奖品抽取完成后才会改变状态
        // 奖品：INIT -> COMPLETED
        // 人员列表：INIT -> COMPLETED

        // 1、奖品
        // 查询活动关联的奖品信息
        // 判断当前状态：如果不是COMPLETED，要扭转
        // 去扭转


        // 2、人员
        // 查询活动关联的人员信息
        // 判断当前状态：如果不是COMPLETED，要扭转
        // 去扭转


        // 3、活动（必须在扭转奖品之后完成)
        // 查询活动信息
        // 判断当前状态：
        //           如果不是COMPLETED，要扭转；
        //           如果不是，抽完全部奖品后，去扭转；
        // 去扭转


        // 4、更新活动完整信息缓存
    }
}
